import re
import sqlalchemy

from sqlalchemy.ext.declarative import DeclarativeMeta, declared_attr
from sqlalchemy import inspect
from sqlalchemy.schema import _get_table_key



camelcase_re = re.compile(r'([A-Z]+)(?=[a-z0-9])')


def camel_to_snake_case(name):
    def _join(match):
        word = match.group()

        if len(word) > 1:
            return ('_%s_%s' % (word[:-1], word[-1])).lower()

        return '_' + word.lower()

    return camelcase_re.sub(_join, name).lstrip('_')


def should_set_tablename(cls):
    if cls.__dict__.get('__abstract__', False) or not any(isinstance(b, DeclarativeMeta) for b in cls.__mro__[1:]):
        return False

    for base in cls.__mro__:
        if '__tablename__' not in base.__dict__:
            continue

        if isinstance(base.__dict__['__tablename__'], declared_attr):
            return False

        return not (
            base is cls or base.__dict__.get('__abstract__', False) or not isinstance(base, DeclarativeMeta)
        )

    return True

class NameMetaMixin(type):
    def __init__(cls, name, bases, d):
        if should_set_tablename(cls):
            cls.__tablename__ = camel_to_snake_case(cls.__name__)

        super(NameMetaMixin, cls).__init__(name, bases, d)

        if '__tablename__' not in cls.__dict__ and '__table__' in cls.__dict__ and cls.__dict__['__table__'] is None:
            del cls.__table__

    def __table_cls__(cls, *args, **kwargs):
        key = _get_table_key(args[0], kwargs.get('schema'))

        if key in cls.metadata.tables:
            return sqlalchemy.Table(*args, **kwargs)

        for arg in args:
            if (isinstance(arg, sqlalchemy.Column) and arg.primary_key) or isinstance(arg, sqlalchemy.PrimaryKeyConstraint):
                return sqlalchemy.Table(*args, **kwargs)

        for base in cls.__mro__[1:-1]:
            if '__table__' in base.__dict__:
                break
        else:
            return sqlalchemy.Table(*args, **kwargs)

        if '__tablename__' in cls.__dict__:
            del cls.__tablename__

class BindMetaMixin(type):
    def __init__(cls, name, bases, d):
        bind_key = d.pop('__bind_key__', None) or getattr(cls, '__bind_key__', None)

        super(BindMetaMixin, cls).__init__(name, bases, d)

        if bind_key is not None and getattr(cls, '__table__', None) is not None:
            cls.__table__.info['bind_key'] = bind_key

class DefaultMeta(NameMetaMixin, BindMetaMixin, DeclarativeMeta):
    pass

class Model(object):
    query_class = None
    query = None

    def __repr__(self):
        identity = inspect(self).identity
        if identity is None:
            pk = "(transient {0})".format(id(self))
        else:
            pk = ', '.join(str(value) for value in identity)
        return '<{0} {1}>'.format(type(self).__name__, pk)

class _SQLAlchemyState(object):
    def __init__(self, db):
        self.db = db
        self.connectors = {}











